package cn.janyd.jmall.web.core.handler;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.janyd.common.core.constants.GlobalErrorCodeConstants;
import cn.janyd.common.core.exception.GlobalException;
import cn.janyd.common.core.exception.ServiceException;
import cn.janyd.common.core.vo.CommonResult;
import cn.janyd.common.core.constants.GlobalErrorConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;

import static cn.janyd.common.core.constants.GlobalErrorCodeConstants.BAD_REQUEST;
import static cn.janyd.common.core.constants.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;

/**
 * 参考 https://github.com/YunaiV/onemall/blob/master-new/common/mall-spring-boot-starter-web/src/main/java/cn/iocoder/mall/web/core/handler/GlobalExceptionHandler.java
 * 全局异常处理
 *
 * @author janyd
 * @since 2020/8/5 20:46
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private final static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    private final MessageSourceAccessor messageSourceAccessor;

    public GlobalExceptionHandler(MessageSourceAccessor messageSourceAccessor) {
        this.messageSourceAccessor = messageSourceAccessor;
    }

    /**
     * 处理SpringMVC请求参数缺失
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        logger.warn("[MissingServletRequestParameterException]", ex);
        return CommonResult.error(BAD_REQUEST, this.messageSourceAccessor.getMessage(GlobalErrorConstants.MISSING_PARAMETER,
                new Object[]{ex.getParameterName()}))
                .setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     * <p>
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
        logger.warn("[MissingServletRequestParameterExceptionHandler]", ex);
        return CommonResult.error(BAD_REQUEST, this.messageSourceAccessor.getMessage(GlobalErrorConstants.PARAMETER_TYPE_ERROR,
                new Object[]{ex.getMessage()}))
                .setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
    }

    /**
     * 处理 SpringMVC 参数校验不正确
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
        //TODO 未完成国际化
        logger.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
        FieldError fieldError = ex.getBindingResult().getFieldError();
        // 断言，避免告警
        assert fieldError != null;
        return CommonResult.error(BAD_REQUEST, this.messageSourceAccessor.getMessage(GlobalErrorConstants.PARAMETER_IS_ERROR,
                new Object[]{fieldError.getDefaultMessage()}))
                .setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
    }

    /**
     * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    public CommonResult<?> bindExceptionHandler(BindException ex) {
        logger.warn("[handleBindException]", ex);
        FieldError fieldError = ex.getFieldError();
        // 断言，避免告警
        assert fieldError != null;
        return CommonResult.error(BAD_REQUEST, this.messageSourceAccessor.getMessage(GlobalErrorConstants.PARAMETER_IS_ERROR,
                new Object[]{fieldError.getDefaultMessage()}))
                .setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {
        logger.warn("[ConstraintViolationExceptionHandler]", ex);
        ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
        return CommonResult.error(BAD_REQUEST, this.messageSourceAccessor.getMessage(GlobalErrorConstants.PARAMETER_IS_ERROR,
                new Object[]{constraintViolation.getMessage()}))
                .setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
    }

    /**
     * 处理 SpringMVC 请求地址不存在
     * <p>
     * 注意，它需要设置如下两个配置项：
     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true
     * 2. spring.mvc.static-path-pattern 为 /statics/**
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
        logger.warn("[NoHandlerFoundExceptionHandler]", ex);
        return CommonResult.error(GlobalErrorCodeConstants.NOT_FOUND, this.messageSourceAccessor.getMessage(GlobalErrorConstants.REQUESTED_ADDRESS_DOES_NOT_EXIST,
                new Object[]{ex.getRequestURL()}))
                .setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
    }

    /**
     * 处理 SpringMVC 请求方法不正确
     * <p>
     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public CommonResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
        logger.warn("[HttpRequestMethodNotSupportedExceptionHandler]", ex);
        return CommonResult.error(GlobalErrorCodeConstants.METHOD_NOT_ALLOWED, this.messageSourceAccessor.getMessage(GlobalErrorConstants.INCORRECT_REQUEST_METHOD))
                .setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
    }

    /**
     * 处理业务异常 ServiceException
     * <p>
     * 例如说，商品库存不足，用户手机号已存在。
     */
    @ExceptionHandler(value = ServiceException.class)
    public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
        logger.info("[ServiceExceptionHandler]", ex);
        return CommonResult.error(ex.getCode(), this.messageSourceAccessor.getMessage(String.valueOf(ex.getCode())));
    }

    /**
     * 处理全局异常 GlobalException
     * <p>
     * 例如说，Dubbo 请求超时，调用的 Dubbo 服务系统异常
     */
    @ExceptionHandler(value = GlobalException.class)
    public CommonResult<?> globalExceptionHandler(GlobalException ex) {
        // 系统异常时，才打印异常日志
        if (INTERNAL_SERVER_ERROR == ex.getCode()) {
            //TODO 持久化日志内容
        } else {
            logger.info("[GlobalExceptionHandler]", ex);
        }
        // 返回 ERROR CommonResult
        return CommonResult.error(ex);
    }

    /**
     * 处理 Dubbo Consumer 本地参数校验时，抛出的 ValidationException 异常
     */
    @ExceptionHandler(value = ValidationException.class)
    public CommonResult<?> validationException(ValidationException ex) {
        logger.warn("[constraintViolationExceptionHandler]", ex);
        // 无法拼接明细的错误信息，因为 Dubbo Consumer 抛出 ValidationException 异常时，是直接的字符串信息，且人类不可读
        return CommonResult.error(BAD_REQUEST, this.messageSourceAccessor.getMessage(GlobalErrorConstants.PARAMETER_IS_ERROR,
                new Object[]{"unknown"}))
                .setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
    }

    /**
     * 处理系统异常，兜底处理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult<?> defaultExceptionHandler(Throwable ex) {
        logger.error("[defaultExceptionHandler]", ex);
        // 返回 ERROR CommonResult
        return CommonResult.error(INTERNAL_SERVER_ERROR, this.messageSourceAccessor.getMessage(String.valueOf(INTERNAL_SERVER_ERROR)))
                .setDetailMessage(ExceptionUtil.getRootCauseMessage(ex));
    }
}
